#pragma once

#include <atomic>
#include <memory>
#include <tuple>

#include "traits_hub.h"
#include "thread_task.h"

class ThreadPool {
 public:
  ThreadPool(int nThreads);
  ~ThreadPool();
  ThreadPool(const ThreadPool &other) = delete;
  ThreadPool &operator=(const ThreadPool &other) = delete;

  template <typename TaskFunc, typename ResultFunc>
  void Submit(TaskFunc &&taskFunc, ResultFunc &&resultFunc) {
    auto task = ThreadTask<TaskFunc, ResultFunc>::template MakeTask(
        std::forward<TaskFunc>(taskFunc), std::forward<ResultFunc>(resultFunc));
    Submit(task);
  }

  void Submit(TaskBase *task);

  void SubmitBulk(ThreadTaskIteratorWrapper wrapper, size_t count);

  template <typename It, typename Func, typename ResultFunc,
            typename = std::enable_if_t<has_operator_minus_v<It>>,
            typename = std::enable_if_t<std::is_copy_constructible_v<It>>>
  void ForEachRange(It begin, It end, Func &&func, ResultFunc &&resultfunc) {
    size_t count = end - begin;
    ForEachN<It, Func>(begin, std::forward<Func>(func), std::move(resultfunc),
                       count);
  }

  template <typename It, typename Func, typename ResultFunc,
            typename = std::enable_if_t<has_operator_plus_prefix_v<It>>,
            typename = std::enable_if_t<std::is_copy_constructible_v<It>>>
  void ForEachN(It begin, Func &&func, ResultFunc &&resultfunc, size_t count) {
    std::shared_ptr<ThreadTaskIteratorBase> iter(
        new ThreadTaskIterator<It, Func, ResultFunc>{
            std::move(begin), std::move(func), std::move(resultfunc), count});
    SubmitBulk(ThreadTaskIteratorWrapper(std::move(iter)), count);
  }
  int GetThreadCount() const;

 private:
  class ThreadPoolInternal;
  std::unique_ptr<ThreadPoolInternal> internal_;
};